(ns reitit.ring-spec-test
  (:require [clojure.spec.alpha :as s]
            [clojure.test :refer [deftest is testing]]
            [reitit.coercion.spec]
            [reitit.core :as r]
            [reitit.ring :as ring]
            [reitit.ring.coercion :as rrc]
            [reitit.ring.spec :as rrs])
  #?(:clj
     (:import (clojure.lang ExceptionInfo))))

(s/def ::role #{:admin :user})
(s/def ::roles (s/and (s/coll-of ::role :into #{}) set?))

(defmulti my-multi (constantly :default))
(defmethod my-multi :default [x] x)

(deftest route-data-validation-test
  (testing "validation is turned off by default"
    (is (r/router?
         (r/router
          ["/api" {:handler "identity"}]))))

  (testing "with default spec validates :name, :handler and :middleware"
    (is (thrown-with-msg?
         ExceptionInfo
         #"Invalid route data"
         (ring/router
          ["/api" {:handler "identity"}]
          {:validate rrs/validate})))
    (is (thrown-with-msg?
         ExceptionInfo
         #"Invalid route data"
         (ring/router
          ["/api" {:handler identity
                   :name "kikka"}]
          {:validate rrs/validate}))))

  (testing "all endpoints are validated"
    (is (thrown-with-msg?
         ExceptionInfo
         #"Invalid route data"
         (ring/router
          ["/api" {:patch {:handler "identity"}}]
          {:validate rrs/validate}))))

  (testing "spec can be overridden"
    (is (r/router?
         (ring/router
          ["/api" {:handler "identity"}]
          {:spec (s/spec any?)
           :validate rrs/validate})))

    (testing "predicates are not allowed"
      (is (thrown-with-msg?
           ExceptionInfo
           #":reitit.ring.spec/invalid-specs"
           (ring/router
            ["/api" {:handler "identity"}]
            {:spec any?
             :validate rrs/validate})))))

  (testing "middleware can contribute to specs"
    (is (r/router?
         (ring/router
          ["/api" {:get {:handler identity
                         :roles #{:admin}}}]
          {:validate rrs/validate
           :data {:middleware [{:spec (s/keys :opt-un [::roles])
                                :wrap (fn [handler]
                                        (fn [request]
                                          (handler request)))}]}})))
    (is (thrown-with-msg?
         ExceptionInfo
         #"Invalid route data"
         (ring/router
          ["/api" {:get {:handler identity
                         :roles #{:adminz}}}]
          {:validate rrs/validate
           :data {:middleware [{:spec (s/keys :opt-un [::roles])
                                :wrap (fn [handler]
                                        (fn [request]
                                          (handler request)))}]}}))))
  (testing "middleware cannot be a list"
    (is (thrown-with-msg?
         ExceptionInfo
         #"Invalid route data"
         (ring/router
          ["/api" {:handler identity
                   :middleware '()}]
          {:validate rrs/validate}))))

  (testing "handler can be a multimethod"
    (is (r/router?
         (ring/router
          ["/api" {:get {:handler my-multi}}]
          {:validate rrs/validate})))))

(deftest coercion-spec-test
  (is (r/router?
       (ring/router
        ["/api"
         ["/plus/:e"
          {:get {:parameters {:query {:a string?}
                              :body {:b string?}
                              :form {:c string?}
                              :header {:d string?}
                              :path {:e string?}}
                 :responses {200 {:body {:total pos-int?}}
                             400 {:description "fail"}
                             500 {}}
                 :handler identity}}]]
        {:data {:middleware [rrc/coerce-exceptions-middleware
                             rrc/coerce-request-middleware
                             rrc/coerce-response-middleware]
                :coercion reitit.coercion.spec/coercion}
         :validate rrs/validate})))

  (is (r/router?
       (ring/router
        ["/api"
         ["/plus/:e"
          {:get {:parameters {:query (s/keys)}
                 :handler identity}}]]
        {:data {:middleware [rrc/coerce-exceptions-middleware
                             rrc/coerce-request-middleware
                             rrc/coerce-response-middleware]
                :coercion reitit.coercion.spec/coercion}
         :validate rrs/validate})))

  (is (thrown-with-msg?
       ExceptionInfo
       #"Invalid route data"
       (ring/router
        ["/api"
         ["/plus/:e"
          {:get {:responses {200 {:description 1}}
                 :handler identity}}]]
        {:data {:middleware [rrc/coerce-exceptions-middleware
                             rrc/coerce-request-middleware
                             rrc/coerce-response-middleware]
                :coercion reitit.coercion.spec/coercion}
         :validate rrs/validate}))))
